%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     玩家离线异步回调管理器
%%% @end
%%%-------------------------------------------------------------------
-module(role_offline_async).

-behaviour(gen_server).

-export([
    login/2
    ,add_event/2
    ,add_event/3
]).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include_lib("stdlib/include/ms_transform.hrl").
-include("common.hrl").
-include("role.hrl").

-define(DAY_SECONDS, 86400).

-define(timer_clear_expire, erlang:send_after(timer:seconds(?DAY_SECONDS), self(), timer_clear_expire)).

-record(state, {next_id = 0}).


%%%===================================================================
%%% API functions
%%%===================================================================

%% 玩家登录调用离线事件
login(#role{rid = Rid, name = Name}, _) ->
    Now = util_time:unixtime(),
    EventQueue = get_event_queue(Rid),
    ?DEBUG("处理玩家[~w,~s]的离线事件:~w",[Rid, Name, EventQueue]),

    Fun = fun
              (#role_offline_async{event = {M, F, A}, expire = Expire}) ->
                  case Now >= Expire of
                      true -> ok;
                      false -> role:apply(async, Rid, {M, F, A})
                  end
          end,
    lists:foreach(Fun, EventQueue),
    delete_event_queue(EventQueue),
    ok.

add_event(Rid, {M, F, A}) ->
    add_event(Rid, {M, F, A}, util_time:unixtime() + ?DAY_SECONDS * 5).
add_event(Rid, Event = {_M, _F, _A}, Expire) ->
    ?DEBUG("玩家(~w)增加离线事件[~w],超时时间:~w",[Rid, Event, util_time:seconds_to_datetime(Expire)]),
    gen_server:cast(?MODULE, {add_event, {Rid, Event, Expire}}).


%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).


init([]) ->
    self() ! init,
    ?timer_clear_expire,

    RegNextId =
        case db_mnesia_info:get(role_offline_async_next_id) of
            NextId0 when is_integer(NextId0) -> NextId0;
            _ -> 1
        end,

    {ok, #state{next_id = RegNextId}}.

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast({add_event, {Rid, Event, Expire}}, State = #state{next_id = NextId}) ->
    NewEvent = #role_offline_async{key = {Rid, NextId}, rid = Rid, event = Event, expire = Expire},
    case db_mnesia:write(role_offline_async, NewEvent) of
        {ok, _} -> skip;
        {error, Reason} -> ?ERR("mnesia error:~w",[Reason])
    end,

    NextIdNew = NextId + 1,
    db_mnesia_info:set(role_offline_async_next_id, NextIdNew),

    {noreply, State#state{next_id = NextIdNew}};
handle_cast(_Request, State) ->
    {noreply, State}.

handle_info(init, State) ->
    clear_expire(),
    {noreply, State};

handle_info(timer_clear_expire, State) ->
    ?timer_clear_expire,
    clear_expire(),
    {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%%===================================================================
%%% Internal functions
%%%===================================================================

clear_expire() ->
    Now = util_time:unixtime(),
    Spec = ets:fun2ms(fun(R = #role_offline_async{expire = Expire}) when Now >= Expire -> R end),

    case db_mnesia:select(role_offline_async, Spec) of
        {ok, List} -> delete_event_queue(List);
        {error, Reason} -> ?ERR("mnesia error:~w",[Reason])
    end.

get_event_queue(Rid) ->
    case db_mnesia:select(role_offline_async, #role_offline_async{rid = Rid, _ = '_'}) of
        {ok, EventQueue} -> EventQueue;
        _ -> []
    end.

delete_event_queue([]) -> ok;
delete_event_queue([#role_offline_async{key = Key} | T]) ->
    case db_mnesia:delete(role_offline_async, Key) of
        {ok, _} -> ok;
        {error, Reason} -> ?ERR("mnesia error:~w",[Reason])
    end,
    delete_event_queue(T).
